home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 2410 / 2410.xpi / components / foxmarks-service.js next >
Text File  |  2010-01-28  |  42KB  |  1,278 lines

  1. /*
  2.  Copyright 2005-2008 Foxmarks Inc.
  3.  
  4.  foxmarks-service.js: component that implements the "service" interface to
  5.  the core synchronization code.
  6.  
  7.  */
  8.  
  9. var Cc = Components.classes;
  10. var Ci = Components.interfaces;
  11. var Xmarks;
  12.  
  13. if(Xmarks === undefined){
  14.     Xmarks = {};
  15. }
  16.  
  17. function LoadJavascript(filename, id) {
  18.     if (id == "undefined") {
  19.         Cc["@mozilla.org/moz/jssubscript-loader;1"].
  20.         getService(Ci.mozIJSSubScriptLoader).
  21.         loadSubScript(filename, null);
  22.     }
  23. }
  24.  
  25. function GetTopWin(wintype) {
  26.     var topwin = Cc['@mozilla.org/appshell/window-mediator;1'].
  27.         getService(Ci.nsIWindowMediator).
  28.         getMostRecentWindow(wintype);
  29.  
  30.     return topwin;
  31. }
  32.  
  33. function FoxmarksSetCookie() {
  34.     var url = "http://www.xmarks.com";
  35.     var d = new Date();
  36.     d.setTime(d.getTime() + (20 * 365 * 24 * 60 * 60 * 1000));
  37.     var cm = Components.classes["@mozilla.org/cookiemanager;1"].
  38.                     getService(Components.interfaces.nsICookieManager2);
  39.  
  40.     cm.add(".xmarks.com", "/", "mid", Xmarks.gSettings.machineId, false, false, false,
  41.             d.getTime() / 1000);
  42. }
  43.  
  44. function FoxmarksLaunchUpgradePage() {
  45.     FoxmarksSetCookie();
  46.  
  47.     upgradeCallback.timer = Cc["@mozilla.org/timer;1"].
  48.         createInstance(Ci.nsITimer);
  49.     upgradeCallback.timer.initWithCallback(upgradeCallback, 5000,
  50.         Ci.nsITimer.TYPE_ONE_SHOT);
  51. }
  52.  
  53. function FoxmarksBuildPostData(num_bookmarks){
  54.     return {
  55.         username: Xmarks.gSettings.username,
  56.         version: Xmarks.FoxmarksVersion(),
  57.         types: {
  58.             bookmarks: {
  59.                 is_enabled: Xmarks.gSettings.isSyncEnabled("bookmarks"),
  60.                 count: num_bookmarks
  61.             },
  62.             passwords: {
  63.                 is_enabled: Xmarks.gSettings.isSyncEnabled("passwords")
  64.             }
  65.         }
  66.     };
  67. }
  68. var upgradeCallback = {
  69.     notify: function(timer) {
  70.         FoxmarksSyncService.server.countItems("bookmarks",
  71.             "bookmark", function(status, num_bookmarks){
  72.             var currver = Xmarks.FoxmarksVersion();
  73.             var wm = Cc['@mozilla.org/appshell/window-mediator;1'].
  74.                 getService(Ci.nsIWindowMediator);
  75.             var topWindow = wm.getMostRecentWindow('navigator:browser');
  76.  
  77.             if(topWindow &&
  78.                 topWindow.document){ 
  79.                 var appcontent =
  80.                     topWindow.document.getElementById("appcontent");
  81.                 var listener = function(e){
  82.                     var doc = e.originalTarget;
  83.                     var button = 
  84.                         doc.getElementById(
  85.                         "set_up_password_sync_button"
  86.                     );
  87.  
  88.                     if(button){
  89.                         button.setAttribute("onclick",  null);
  90.                         button.addEventListener(
  91.                             "click",
  92.                             function(){
  93.                                 var os = Cc["@mozilla.org/observer-service;1"].
  94.                                     getService(Ci.nsIObserverService);
  95.                                 os.notifyObservers(null, 
  96.                                     "foxmarks-showsettingpane", 
  97.                                     "foxmarks-syncpane"
  98.                                 );
  99.                             }, 
  100.                             true
  101.                         );
  102.                         appcontent.removeEventListener(
  103.                             "DOMContentLoaded",
  104.                             listener, 
  105.                             false
  106.                         );
  107.                     }
  108.                 };
  109.                 if(appcontent){
  110.                     appcontent.addEventListener(
  111.                         "DOMContentLoaded",
  112.                         listener, 
  113.                         false
  114.                     );
  115.                 }
  116.             }
  117.             // We used to set the protocol as Xmarks.gSettings.httpProtocol,
  118.             // but that causes warnings when that page references non-HTTPS
  119.             // ads, so we peg the protocol as http.
  120.             Xmarks.OpenInNewTab("http://" + Xmarks.gSettings.webHost + 
  121.                 "/firefox/upgrade/" + currver, true, 
  122.                 FoxmarksBuildPostData(num_bookmarks));
  123.             Xmarks.gSettings.currVersion = currver;
  124.         });
  125.     }
  126. }
  127. function FoxmarksCheckForServerChanges() {
  128.     if(Xmarks.gSettings.useOwnServer){
  129.         var os = Cc["@mozilla.org/observer-service;1"]
  130.             .getService(Ci.nsIObserverService);
  131.         os.notifyObservers(null, "foxmarks-realstart", "");
  132.     } else {
  133.         var url = Xmarks.gSettings.httpProtocol + Xmarks.gSettings.apiHost +
  134.             "/internal/serverprefs";
  135.         var request = new Request(
  136.             "POST", 
  137.             url,
  138.             {
  139.                 mid: Xmarks.gSettings.machineId,
  140.                 version: Xmarks.gSettings.serverVersion,
  141.                 abgroup: Xmarks.gSettings.abgroup,
  142.                 cid: "xmfx"
  143.             },
  144.             {
  145.                 isAuthRequest: false,
  146.                 headers: null,
  147.                 ignoreBody: false,
  148.                 ignoreAuthTokens: true
  149.             }
  150.         );
  151.         request.Start(function(response){
  152.             if(response && response.status === 0){
  153.  
  154.                 if(response.version > Xmarks.gSettings.serverVersion){
  155.                     if(response.prefs){ 
  156.                         var prefs = response.prefs;
  157.                         for(var pref in prefs){
  158.                             if(prefs.hasOwnProperty(pref)){
  159.                                 Xmarks.gSettings[pref] = prefs[pref];
  160.                             }
  161.                         }
  162.                     }
  163.                     Xmarks.gSettings.serverVersion = response.version;
  164.                 }
  165.             }
  166.             var os = Cc["@mozilla.org/observer-service;1"]
  167.                 .getService(Ci.nsIObserverService);
  168.             os.notifyObservers(null, "foxmarks-realstart", "");
  169.         });
  170.     }
  171. }
  172.  
  173. function FoxmarksLaunchSetupWizard() {
  174.     FoxmarksSetCookie();
  175.     FoxmarksWizardCallback.timer = Cc["@mozilla.org/timer;1"].
  176.         createInstance(Ci.nsITimer);
  177.     FoxmarksWizardCallback.timer.initWithCallback(FoxmarksWizardCallback, 5000,
  178.         Ci.nsITimer.TYPE_ONE_SHOT);
  179. }
  180.  
  181. var FoxmarksWizardCallback = {
  182.     notify: function(timer) {
  183.         var url = "http://" + Xmarks.gSettings.staticHost + Xmarks.gSettings.wizardPrefs;
  184.         var request = new Request(
  185.             "GET", 
  186.             url,
  187.             null,
  188.             {
  189.                 isAuthRequest: false,
  190.                 headers: null,
  191.                 ignoreBody: false,
  192.                 ignoreAuthTokens: true
  193.             }
  194.         );
  195.         request.Start(function(response){
  196.             var os = Cc["@mozilla.org/observer-service;1"]
  197.                 .getService(Ci.nsIObserverService);
  198.             var JSON = Cc["@mozilla.org/dom/json;1"].
  199.                 createInstance(Ci.nsIJSON);
  200.             os.notifyObservers(null, "foxmarks-newpopup", JSON.encode(response));
  201.         });
  202.     }
  203. }
  204.  
  205. function HandleShutdown(cancel) {
  206.     var retval = { helpurl: null };
  207.     var sb = Xmarks.Bundle().GetStringFromName;
  208.     var dontask = {value: false};
  209.     var rv = 0;
  210.  
  211.     if (!Xmarks.gSettings.haveSynced || GetState() != "dirty") {
  212.         return;
  213.     }
  214.  
  215.     var topwin = GetTopWin();
  216.  
  217.     if (!topwin) {
  218.         Xmarks.LogWrite("HandleShutdown: Couldn't find a topwin!");
  219.         return;
  220.     }
  221.  
  222.     if (Xmarks.gSettings.syncOnShutdown && Xmarks.gSettings.syncOnShutdownAsk) {
  223.  
  224.         rv = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  225.         getService(Ci.nsIPromptService).
  226.         confirmEx(topwin, sb("appname.long"), sb("msg.unsynced"),
  227.             Ci.nsIPromptService.STD_YES_NO_BUTTONS, null, null, null,
  228.             sb("msg.dontask"), dontask);
  229.         // Reverse sense: confirmEx returns 0 - yes, 1 - no
  230.         rv = !rv;
  231.  
  232.         // If user says "don't ask me again", set syncOnShutdown to whatever
  233.         // they have chosen in this instance.
  234.         if (dontask.value) {
  235.             Xmarks.gSettings.syncOnShutdown = rv;
  236.         }
  237.         Xmarks.gSettings.syncOnShutdownAsk = !dontask.value;
  238.  
  239.     } else {                           // don't ask
  240.         rv = Xmarks.gSettings.syncOnShutdown;
  241.     }
  242.  
  243.     if (rv) {
  244.         var win = topwin.openDialog(
  245.             "chrome://foxmarks/content/foxmarks-progress.xul", "_blank",
  246.             "chrome,dialog,modal,centerscreen", "synch", retval, null);
  247.         if (retval.helpurl) { // we hit an error and user pressed help button
  248.             if (cancel instanceof Ci.nsISupportsPRBoolean) {
  249.                 cancel.value = true;
  250.                 topwin.openDialog("chrome://browser/content/browser.xul",
  251.                     "_blank", "chrome,all,dialog=no", retval.helpurl);
  252.             }
  253.         }
  254.     }
  255. }
  256.  
  257. function LoadFiles() {
  258.     LoadJavascript("chrome://foxmarks/content/foxmarks-log.js",
  259.         typeof(Xmarks.LogWrite));
  260.     LoadJavascript("chrome://foxmarks/content/foxmarks-settings.js",
  261.         typeof(Xmarks.gSettings));
  262.     LoadJavascript("chrome://foxmarks/content/foxmarks-update.js",
  263.         typeof(ForceUpdate));
  264.     LoadJavascript("chrome://foxmarks/content/foxmarks-clobber.js",
  265.         typeof(onClobberCancel));
  266.     LoadJavascript("chrome://foxmarks/content/foxmarks-bookmark.js",
  267.         typeof(loadDatasourceSet));
  268.     if ("@mozilla.org/browser/nav-bookmarks-service;1" in Cc)
  269.         LoadJavascript("chrome://foxmarks/content/foxmarks-places.js",
  270.             typeof(BookmarkDatasource));
  271.     else
  272.         LoadJavascript("chrome://foxmarks/content/foxmarks-rdf.js",
  273.             typeof(BookmarkDatasource));
  274.     LoadJavascript("chrome://foxmarks/content/foxmarks-nodes.js",
  275.         typeof(Node));
  276.     LoadJavascript("chrome://foxmarks/content/foxmarks-command.js",
  277.         typeof(Command));
  278.     LoadJavascript("chrome://foxmarks/content/foxmarks-core.js",
  279.         typeof(Synchronize));
  280.     LoadJavascript("chrome://foxmarks/content/foxmarks-network.js",
  281.         typeof(Request));
  282.     LoadJavascript("chrome://foxmarks/content/foxmarks-json.js",
  283.         "undefined");
  284.  
  285.     LoadJavascript("chrome://foxmarks/content/shared/Base64.js",
  286.         typeof(Base64));
  287.     LoadJavascript("chrome://foxmarks/content/shared/CreateAESManager.js",
  288.         typeof(CreateAESManager));
  289.     LoadJavascript("chrome://foxmarks/content/foxmarks-utils.js",
  290.         typeof(forEach));
  291.     LoadJavascript("chrome://foxmarks/content/foxmarks-unittest.js",
  292.         typeof(gFoxmarksUT));
  293.     LoadJavascript("chrome://foxmarks/content/foxmarks-uitools.js",
  294.         typeof(Xmarks.OpenWindowByType));
  295.     if("@mozilla.org/login-manager;1" in Cc){
  296.         LoadJavascript("chrome://foxmarks/content/foxmarks-password.js",
  297.             typeof(PasswordDatasource));
  298.     }
  299.     LoadJavascript("chrome://foxmarks/content/foxmarks-server.js",
  300.         typeof(SyncServer));
  301. }
  302.  
  303. var logStream = null;
  304.  
  305. function removeTempLogFile(){
  306.     var fileremoved = Cc['@mozilla.org/file/directory_service;1']
  307.         .getService(Ci.nsIProperties)
  308.         .get('ProfD', Ci.nsIFile);
  309.     fileremoved.append("xmarks.temp.log");
  310.     try {
  311.         fileremoved.remove(false);
  312.     } catch(e){}
  313. }
  314. function logMoveFile(){
  315.     try {
  316.         var file = Cc['@mozilla.org/file/directory_service;1']
  317.             .getService(Ci.nsIProperties)
  318.             .get('ProfD', Ci.nsIFile);
  319.         file.append("xmarks.log");
  320.  
  321.         var dir = Cc['@mozilla.org/file/directory_service;1']
  322.             .getService(Ci.nsIProperties)
  323.             .get('ProfD', Ci.nsIFile);
  324.         removeTempLogFile();
  325.         file.moveTo(dir, "xmarks.temp.log");
  326.  
  327.  
  328.         var fromstream = Cc["@mozilla.org/network/file-input-stream;1"]
  329.             .createInstance(Ci.nsIFileInputStream);
  330.         var tostream = Cc["@mozilla.org/network/file-output-stream;1"]
  331.             .createInstance(Ci.nsIFileOutputStream);
  332.  
  333.         fromstream.init(file, -1, 0x01, 0);
  334.         var logSeek = fromstream.QueryInterface(Ci.nsISeekableStream);
  335.         var lread = fromstream.QueryInterface(Ci.nsILineInputStream);
  336.         var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
  337.             .createInstance(Ci.nsIScriptableUnicodeConverter);
  338.         converter.charset = "UTF-8";
  339.         var i =  100 * 1024;
  340.         if(i > file.fileSize){
  341.             i = file.fileSize;
  342.         }
  343.  
  344.         var filenew = Cc['@mozilla.org/file/directory_service;1']
  345.             .getService(Ci.nsIProperties)
  346.             .get('ProfD', Ci.nsIFile);
  347.         filenew.append("xmarks.log");
  348.         tostream.init(filenew, 0x02 | 0x08 | 0x10, 0664, 0);
  349.  
  350.         logSeek.seek(logSeek.NS_SEEK_END, -i);
  351.  
  352.         var buf;
  353.         var cont = true;
  354.         var lineData = {};
  355.         var ctr = 0;
  356.         // throw out the first one; could be mid line
  357.         cont = lread.readLine(lineData);
  358.         while(cont){
  359.             lineData = {};
  360.             cont = lread.readLine(lineData);
  361.             if(cont){
  362.                 buf = converter.ConvertToUnicode(lineData.value) + "\n";
  363.                 tostream.write(buf, buf.length);
  364.             }
  365.         }
  366.  
  367.      } catch(e){
  368.         Components.utils.reportError(e);
  369.      } finally {
  370.         if(fromstream !== undefined)
  371.             fromstream.close();
  372.         if(tostream !== undefined)
  373.             tostream.close();
  374.      }
  375.     removeTempLogFile();
  376. }
  377. function logFileOpen() {
  378.     var file = Cc['@mozilla.org/file/directory_service;1']
  379.     .getService(Ci.nsIProperties)
  380.     .get('ProfD', Ci.nsIFile);
  381.  
  382.     var needsTruncate = false;
  383.     var filesize = 0;
  384.     file.append("xmarks.log");
  385.  
  386.     // check the file size
  387.     try {
  388.        if(file.isFile()){
  389.             filesize = file.fileSize;
  390.        }
  391.  
  392.        if(filesize > 500 * 1024 && Xmarks.gSettings.truncateLog ){
  393.             logMoveFile();
  394.        }
  395.     } catch(e) {
  396.    //     Components.utils.reportError(e);
  397.     }
  398.     
  399.     try {
  400.         logStream = Cc["@mozilla.org/network/file-output-stream;1"]
  401.         .createInstance(Ci.nsIFileOutputStream);
  402.  
  403.         // use write, append, create
  404.         logStream.init(file, 0x02 | 0x08 | 0x10, 0664, 0);
  405.     } catch (e) {
  406.         // We failed to open. Close and try again next time.
  407.         logFileClose();
  408.     }
  409. }
  410.  
  411. function logFileClose() {
  412.     try {
  413.         logStream.close();
  414.     } catch (e) {}
  415.     logStream = null;
  416. }
  417.  
  418. var gFailureCount = 0;// number of consecutive times we've failed
  419. var gBackoffUntil = 0;// if we've been failing, our backoff time (ms since 1970)
  420. var gServerBackoff = 
  421.     { 'bookmarks': 0,
  422.       'passwords': 0,
  423.       min: function(){
  424.         return Math.min(this['bookmarks'], this['passwords']);
  425.       },
  426.       max: function(){
  427.         return Math.max(this['bookmarks'], this['passwords']);
  428.       },
  429.       clear: function(){
  430.         this['bookmarks'] = 0;
  431.         this['passwords'] = 0;
  432.       }
  433.     };  // seconds server wants us to back off
  434.  
  435. function ReturnErrorMsg(code, msg, restoreState, noBackoff) {
  436.     Xmarks.gSettings.lastError  = code;
  437.     Xmarks.gSettings.lastError  = code;
  438.     Xmarks.Notify({status: code, msg: msg });
  439.     ClearBusy();
  440.     SetState(restoreState ? restoreState : ((code == 503 || code == 2)
  441.             ? "dirty" : "error"));
  442.     Xmarks.LogWrite("Returned error: " + msg + "(" + code + ")");
  443.     if(code != 2 && !noBackoff){
  444.         var d = new Date();
  445.         // Initial back-off is 15 minutes, doubling with each error
  446.         gBackoffUntil = d.getTime() + 1000 * (Math.max(
  447.             15 * 60 * Math.pow(2, gFailureCount++),
  448.             gServerBackoff.max()) + Math.floor(Math.random() * 15));
  449.         gServerBackoff.clear();;
  450.         var retry = new Date();
  451.         retry.setTime(gBackoffUntil);
  452.         Xmarks.LogWrite("Will retry at " + retry);
  453.     }
  454.     FoxmarksSyncService.lastError = code;
  455. }
  456.  
  457. function ReturnErrorCode(code) {
  458.     ReturnErrorMsg(code, Xmarks.MapError(code));
  459. }
  460.  
  461. function ReturnSuccess(msgname, args, restoreState) {
  462.     if (args == null) {
  463.         var args = {};
  464.     }
  465.  
  466.     args.status = 0;
  467.     args.msg = Xmarks.Bundle().GetStringFromName(msgname);
  468.  
  469.     SetState(restoreState ? restoreState : "ready");
  470.     ClearBusy();
  471.     gFailureCount = 0;
  472.     FoxmarksSyncService.lastError = 0;
  473.     Xmarks.LogWrite("Success: " + Xmarks.Bundle().GetStringFromName(msgname));
  474.     Xmarks.Notify(args);
  475. }
  476.  
  477. function SetBusy() {
  478.     if (IsBusy._busy) {
  479.         return false;
  480.     } else {
  481.         IsBusy._busy = true;
  482.         SetState("working");
  483.         return true;
  484.     }
  485. }
  486.  
  487. function ClearBusy() {
  488.     IsBusy._busy = false;
  489. }
  490.  
  491. function IsBusy() {
  492.     return IsBusy._busy;
  493. }
  494. IsBusy._busy = false;
  495.  
  496. function GetState() {
  497.     return GetState._state;
  498. }
  499. GetState._state = "ready";
  500.  
  501. function SetState(newstate) {
  502.     if (newstate == GetState()) {
  503.         return;
  504.     }
  505.  
  506.     var os = Cc["@mozilla.org/observer-service;1"]
  507.     .getService(Ci.nsIObserverService);
  508.  
  509.     os.notifyObservers(null, "foxmarks-statechange", newstate);
  510.  
  511.     GetState._state = newstate;
  512. }
  513.  
  514. // Internal callbacks
  515.  
  516. function LogStart(op, dest) {
  517.     Xmarks.LogWrite("------ Xmarks/" + Xmarks.FoxmarksVersion() + " (" + 
  518.             FoxmarksSyncService.getStorageEngine("bookmarks") + ") starting " + op +
  519.             " with " + dest + " ------");
  520. }
  521.  
  522. ///////////////////////////////////////////////////////////////////////////
  523. //
  524. // nsFoxmarksService
  525. //
  526.  
  527. var FoxmarksSyncService = null;  // set during initialization to instance object
  528.  
  529. function nsFoxmarksService() {
  530.     LoadFiles();
  531. }
  532.  
  533. nsFoxmarksService.prototype = {
  534.     /////////////////////////////////////////////////////////////////////////
  535.     // nsIFoxmarksService
  536.  
  537.     timer: null,
  538.     _server: null,
  539.     lastmodified: null,
  540.  
  541.     status: function(syncType) {
  542.         // return if we're currently processing another request
  543.         if (!SetBusy()) {
  544.             return false;
  545.         }
  546.         LogStart("status", Xmarks.gSettings.host);
  547.         if(syncType === undefined)
  548.             syncType = "bookmarks";
  549.  
  550.         this.server.status(syncType, function(status, response){
  551.             if (status) {
  552.                 ReturnErrorCode(status);
  553.             }
  554.             else {
  555.                 ReturnSuccess("msg.accountverified", response);
  556.             }
  557.         });
  558.         return true;
  559.     },
  560.     extstatus: function(syncType) {
  561.         // return if we're currently processing another request
  562.         if (!SetBusy()) {
  563.             return false;
  564.         }
  565.         LogStart("extstatus", Xmarks.gSettings.host);
  566.         if(syncType === undefined)
  567.             syncType = "bookmarks";
  568.  
  569.         this.server.extstatus(syncType, function(status, response){
  570.             if (status) {
  571.                 ReturnErrorCode(status);
  572.             }
  573.             else {
  574.                 ReturnSuccess("msg.accountverified", response);
  575.             }
  576.         });
  577.         return true;
  578.     },
  579.     purgepasswords: function(){
  580.         LogStart("purgepasswords", Xmarks.gSettings.host);
  581.         this.server.purgepasswords(function(status){
  582.             if (!status) {
  583.                 ReturnSuccess("msg.synccompleted");
  584.             }
  585.             else {
  586.                 ReturnErrorCode(status);
  587.             }
  588.         });
  589.         return true;
  590.  
  591.     },
  592.  
  593.     restore: function(rev) {
  594.         // return if we're currently processing another request
  595.         if (!SetBusy()) {
  596.             return false;
  597.         }
  598.         LogStart("Restoring to Revision: " + rev, Xmarks.gSettings.host);
  599.  
  600.         this.server.restore(rev, function(status, response){
  601.             if (status) {
  602.                 ReturnErrorCode(status);
  603.             }
  604.             else {
  605.                 ReturnSuccess("msg.restore", response);
  606.             }
  607.         });
  608.         return true;
  609.     },
  610.     getrevision: function(rev) {
  611.         // return if we're currently processing another request
  612.         if (!SetBusy()) {
  613.             return false;
  614.         }
  615.         LogStart("Getting Revision: " + rev, Xmarks.gSettings.host);
  616.  
  617.         this.server.getrevision(rev, function(status, response){
  618.             if (status) {
  619.                 ReturnErrorCode(status);
  620.             }
  621.             else {
  622.                 ReturnSuccess("msg.getrevision", response);
  623.             }
  624.         });
  625.         return true;
  626.     },
  627.  
  628.     getrevisions: function() {
  629.         // return if we're currently processing another request
  630.         if (!SetBusy()) {
  631.             return false;
  632.         }
  633.         LogStart("getrevisions", Xmarks.gSettings.host);
  634.  
  635.         this.server.getrevisions(function(status, response){
  636.             if (status) {
  637.                 ReturnErrorCode(status);
  638.             }
  639.             else {
  640.                 ReturnSuccess("msg.getrevisions", response);
  641.             }
  642.         });
  643.         return true;
  644.     },
  645.     verifypin: function(pin) {
  646.         // return if we're currently processing another request
  647.         if (!SetBusy()) {
  648.             return false;
  649.         }
  650.         LogStart("verify pin", Xmarks.gSettings.host);
  651.  
  652.         this.server.verifypin(pin, function(status, response){
  653.             if (status) {
  654.                 ReturnErrorCode(status);
  655.             }
  656.             else {
  657.                 ReturnSuccess("msg.pinverified", response);
  658.             }
  659.         });
  660.         return true;
  661.     },
  662.  
  663.     synchronize: function(automatic) {
  664.         var prevState = GetState();
  665.  
  666.         // return if we're currently processing another request
  667.         if (!SetBusy()) {
  668.             return false;
  669.         }
  670.  
  671.         LogStart("sync", Xmarks.gSettings.host);
  672.         this.server.manual = automatic === true ? false : true;
  673.         this.server.sync(prevState, function(status){
  674.             if (!status) {
  675.                 ReturnSuccess("msg.synccompleted");
  676.             }
  677.             else {
  678.                 ReturnErrorCode(status);
  679.             }
  680.         });
  681.         return true;
  682.     },
  683.  
  684.     synchronizeInitial: function (remoteIsMaster, doMerge) {
  685.         // return if we're currently processing another request
  686.         if (!SetBusy()) {
  687.             return false;
  688.         }
  689.  
  690.         LogStart("initial sync", Xmarks.gSettings.host);
  691.         this.server.manual = true;
  692.  
  693.         // We need to self correct for password sync.  If there is an
  694.         // uploadReq == true, we know it doesn't have data on the server.
  695.         // if it's false, we should force a merge per our PRD.
  696.         if(Xmarks.gSettings.isSyncEnabled("passwords")){
  697.             // Set default higher if we're doing password sync
  698.             Xmarks.gSettings.securityLevel = 1;
  699.             if(!Xmarks.gSettings.mustUpload("passwords")){
  700.                 Xmarks.gSettings.setMustMerge("passwords", true);
  701.             }
  702.         }
  703.  
  704.         if (doMerge) {
  705.             this.server.merge(!remoteIsMaster, Finished);
  706.         } else {
  707.             if (remoteIsMaster) {
  708.                 this.server.download(Finished);
  709.             } else {
  710.                 this.server.upload(Finished);
  711.             }
  712.         }
  713.  
  714.         return true;
  715.  
  716.         function Finished(status) {
  717.             if (!status) {
  718.                 ReturnSuccess("msg.synccompleted");
  719.             } else {
  720.                 ReturnErrorCode(status);
  721.             }
  722.         }
  723.     },
  724.  
  725.     upload: function () {
  726.         // return if we're currently processing another request
  727.         if (!SetBusy()) {
  728.             return false;
  729.         }
  730.  
  731.         LogStart("upload", Xmarks.gSettings.host);
  732.  
  733.         this.server.manual = true;
  734.         this.server.upload(function(status){
  735.             if (!status) {
  736.                 ReturnSuccess("msg.uploadcompleted");
  737.             }
  738.             else {
  739.                 ReturnErrorCode(status);
  740.             }
  741.         });
  742.         return true;
  743.     },
  744.  
  745.     download: function () {
  746.         // return if we're currently processing another request
  747.         if (!SetBusy()) {
  748.             return false;
  749.         }
  750.  
  751.         LogStart("download", Xmarks.gSettings.host);
  752.  
  753.         this.server.manual = true;
  754.         this.server.download(function(status){
  755.             if (!status) {
  756.                 ReturnSuccess("msg.remotefilecopied");
  757.             }
  758.             else {
  759.                 ReturnErrorCode(status);
  760.             }
  761.         });
  762.         return true;
  763.     },
  764.  
  765.     _handleDataResponse: function(response){
  766.         var os = Cc["@mozilla.org/observer-service;1"]
  767.             .getService(Ci.nsIObserverService);
  768.  
  769.         os.notifyObservers(null, "foxmarks-dataservice", response.toSource());
  770.     },
  771.     getSimilarSites: function(url) {
  772.         LogStart("getSimilarSites", Xmarks.gSettings.apiHost);
  773.         this.server.getSimilarSites(url, this._handleDataResponse);
  774.         return true;
  775.     },
  776.     getTurboTags: function(url) {
  777.         LogStart("getTurboTags", Xmarks.gSettings.apiHost);
  778.         this.server.getTurboTags(url, this._handleDataResponse);
  779.         return true;
  780.     },
  781.     updateReview: function(url, url_id, rating, review) {
  782.         LogStart("updateReview", Xmarks.gSettings.apiHost);
  783.         this.server.updateReview(url, url_id, rating, review, 
  784.             this._handleDataResponse);
  785.         return true;
  786.     },
  787.     getProfileNames: function() {
  788.         var prevState = GetState();
  789.  
  790.         var funcFinished = function(status, response) {
  791.             if (!status) {
  792.                 Xmarks.LogWrite("GetProfileNames succeeded; response is " +
  793.                         response.toSource());
  794.                 ReturnSuccess("error.0", response, prevState);
  795.             } else {
  796.                 ReturnErrorMsg(status, Xmarks.MapError(status), prevState);
  797.             }
  798.         };
  799.         // return if we're currently processing another request
  800.         if (!SetBusy()) {
  801.             return false;
  802.         }
  803.  
  804.         LogStart("getProfileNames", Xmarks.gSettings.acctMgrHost);
  805.  
  806.         if (this.server.getProfileNames) {
  807.             this.server.getProfileNames(funcFinished);
  808.             return true;
  809.         } else {
  810.             return false;
  811.         }
  812.  
  813.     },
  814.  
  815.     launchSuccessPage: function(){
  816.         this.server.countItems("bookmarks",
  817.             "bookmark", function(status, num_bookmarks){
  818.             var currver = Xmarks.FoxmarksVersion();
  819.             // We used to set the protocol from Xmarks.gSettings.httpProtocol
  820.             // but that causes warnings when the dest page contains non-HTTPS
  821.             // content, so we peg the protocol as http. 
  822.             Xmarks.OpenInNewTab("http://" + Xmarks.gSettings.webHost + 
  823.                 "/firefox/success/" + currver, true, 
  824.                 FoxmarksBuildPostData(num_bookmarks));
  825.             Xmarks.gSettings.currVersion = currver;
  826.         });
  827.     },
  828.     cancel: function() {
  829.         this.server.cancel();
  830.     },
  831.     datacancel: function() {
  832.         this.server.datacancel();
  833.     },
  834.  
  835.     logWrite: function (msg) {
  836.         if (!logStream)
  837.             logFileOpen();
  838.  
  839.         var d = new Date();
  840.         var year = d.getFullYear();
  841.         var month = d.getMonth() + 1; if (month < 10) month = "0" + month;
  842.         var day = d.getDate(); if (day < 10) day = "0" + day;
  843.         var hour = d.getHours(); if (hour < 10) hour = "0" + hour;
  844.         var minute = d.getMinutes(); if (minute < 10) minute = "0" + minute;
  845.         var sec = d.getSeconds(); if (sec < 10) sec = "0" + sec;
  846.  
  847.         // format is [YYYY-MM-DD HH:MM:SS] msg\n
  848.         var string = "[" + year + "-" + month + "-" + day + " " + hour + ":" +
  849.             minute + ":" + sec + "] " + msg + "\n";
  850.  
  851.         if(Xmarks.gSettings.getDebugOption("dumplog"))
  852.             dump("Xmarks: " + string + "\n");
  853.         logStream.write(string, string.length)
  854.     },
  855.  
  856.     getState: function() {
  857.         return GetState();
  858.     },
  859.  
  860.     _password: null,
  861.     _pin: null,
  862.     _auth: null,
  863.  
  864.     getAuth: function() {
  865.         return this._auth;
  866.     },
  867.  
  868.     setAuth: function(auth) {
  869.         this._auth = auth;
  870.     },
  871.     getPassword: function() {
  872.         return this._password;
  873.     },
  874.  
  875.     setPassword: function(password) {
  876.         this._password = password;
  877.     },
  878.     getPin: function() {
  879.         return this._pin;
  880.     },
  881.  
  882.     setPin: function(password) {
  883.         this._pin = password;
  884.     },
  885.  
  886.     getStorageEngine: function(synctype) {
  887.         return getDatasourceAttribute(synctype, "engine");
  888.     },
  889.  
  890.     getLastError: function() {
  891.         return this.lastError;
  892.     },
  893.  
  894.     lastError: 0,
  895.     _uninstall: false,
  896.     _channel: null,
  897.     _pingListener: {
  898.          onStartRequest: function () {},
  899.          onDataAvailable: function () {},
  900.          onStopRequest: function () {},
  901.          onChannelRedirect: function (aOldChannel, aNewChannel, aFlags) {
  902.             this._newchannel = aNewChannel;
  903.          },
  904.         getInterface: function (aIID) {
  905.             try {
  906.                 return this.QueryInterface(aIID);
  907.             } catch (e) {
  908.                 throw Components.results.NS_NOINTERFACE;
  909.             }
  910.         },
  911.         onProgress : function (aRequest, aContext, aProgress, aProgressMax) { },
  912.         onStatus : function (aRequest, aContext, aStatus, aStatusArg) { },
  913.         onRedirect : function (aOldChannel, aNewChannel) { },
  914.         QueryInterface : function(aIID) {
  915.             if (aIID.equals(Components.interfaces.nsISupports) ||
  916.                 aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
  917.                 aIID.equals(Components.interfaces.nsIChannelEventSink) || 
  918.                 aIID.equals(Components.interfaces.nsIProgressEventSink) ||
  919.                 aIID.equals(Components.interfaces.nsIHttpEventSink) ||
  920.                 aIID.equals(Components.interfaces.nsIStreamListener))
  921.             return this;
  922.             throw Components.results.NS_NOINTERFACE;
  923.          } 
  924.      },
  925.      pingServer: function(topic){
  926.         var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  927.             .getService(Components.interfaces.nsIIOService);
  928.  
  929.         // create an nsIURI
  930.         var attrs = [];
  931.         var session = Date.now().toString(36);
  932.         attrs.push("app="       + "jezebel");
  933.         attrs.push("mid="       + Xmarks.gSettings.machineId);
  934.         attrs.push("abgroup="       + Xmarks.gSettings.abgroup);
  935.         attrs.push("sess="      + session);
  936.         attrs.push("page="      + topic);
  937.         attrs.push("username="  + Xmarks.gSettings.username);
  938.         attrs.push("no_cache="  + Date.now().toString(36));
  939.  
  940.         var query = attrs.join("&");
  941.         var url = Xmarks.gSettings.httpProtocol + "tr.xmarks.com/tracking/impressions.gif?" + query;
  942.         this._pingRequest = new Request(
  943.             "GET",
  944.             url, 
  945.             null, 
  946.             {
  947.                 isAuthRequest: false,
  948.                 headers: null,
  949.                 ignoreBody: true,
  950.                 ignoreAuthTokens: true
  951.             }
  952.         );
  953.         this._pingRequest.Start(function(){});
  954.     },
  955.     getLastModified: function(synctype) {
  956.         return FoxmarksSyncService.lastmodified;
  957.     },
  958.  
  959.     get server() {
  960.         if (!this._server) {
  961.             this._server = new SyncServer();
  962.         }
  963.         return this._server;
  964.     },
  965.  
  966.     /////////////////////////////////////////////////////////////////////////
  967.     //
  968.     // nsIObserver
  969.  
  970.     observe: function(subject, topic, data)  { // called at startup
  971.         var timerCallback = {
  972.  
  973.             notify: function(timer) {
  974.  
  975.                 var now = new Date().getTime();
  976.  
  977.                 // scan entire bookmark set to find
  978.                 // last modified date for entire set
  979.                 // XXX: Implement
  980.  
  981.  
  982.                 // Do automatic sync if necessary
  983.                 if (!IsBusy() && (!gFailureCount || now > gBackoffUntil) &&
  984.                     Xmarks.gSettings.synchOnTimer && Xmarks.gSettings.haveSynced) {
  985.                     if (Xmarks.gSettings.minutesSinceLastSync > 
  986.                             Xmarks.gSettings.autoSynchFreq) {
  987.                         FoxmarksSyncService.synchronize(true);
  988.                     } else {
  989.                         if (FoxmarksSyncService.lastmodified && Xmarks.gSettings.haveSynced && 
  990.                             FoxmarksSyncService.lastmodified >
  991.                             Date.parse(Xmarks.gSettings.lastSynchDate) &&
  992.                             now - FoxmarksSyncService.lastmodified > 5 * 60 * 1000) {
  993.                             FoxmarksSyncService.synchronize(true);
  994.                         }
  995.                     }
  996.                 }
  997.             }
  998.         }
  999.  
  1000.         if (topic == "app-startup") {
  1001.             // Pre-initialization here.
  1002.             var os = Cc["@mozilla.org/observer-service;1"].
  1003.                 getService(Ci.nsIObserverService);
  1004.             os.addObserver(this, "quit-application-requested", false);
  1005.             os.addObserver(this, "foxmarks-datasourcechanged", false);
  1006.             os.addObserver(this, "foxmarks-rununittest", false);
  1007.             os.addObserver(this, "foxmarks-unittesterror", false);
  1008.             os.addObserver(this, "foxmarks-tr", false);
  1009.             os.addObserver(this, "earlyformsubmit", false);
  1010.             os.addObserver(this, "final-ui-startup", false);
  1011.             os.addObserver(this, "em-action-requested", false);
  1012.             os.addObserver(this, "quit-application-granted", false);
  1013.             os.addObserver(this, "foxmarks-showsettingpane", false);
  1014.             os.addObserver(this, "foxmarks-realstart", false);
  1015.         } else if (topic == "final-ui-startup") {
  1016.             // check to see if crypto hash is installed (bug #7397)
  1017.             // if we error, wait a while and try again
  1018.             var counter = 0;
  1019.             var cryptoCallback = {
  1020.                 notify: function() {
  1021.                     // give it another shot
  1022.                     try {
  1023.                         var hash = Cc["@mozilla.org/security/hash;1"].
  1024.                                     createInstance(Ci.nsICryptoHash);
  1025.                     // otherwise, wait again if we have time
  1026.                     } catch(e){
  1027.                         counter++;
  1028.  
  1029.                         Xmarks.LogWrite("Error: Cryptohash not initialized");
  1030.                         if(counter < 10){
  1031.                             var timer = Cc["@mozilla.org/timer;1"].
  1032.                                 createInstance(Ci.nsITimer);
  1033.                             timer.initWithCallback(cryptoCallback, 500,
  1034.                                 Ci.nsITimer.TYPE_ONE_SHOT);
  1035.                         }
  1036.                         return;
  1037.                     }
  1038.                     FoxmarksCheckForServerChanges();
  1039.                 }
  1040.             };
  1041.  
  1042.             var timer = Cc["@mozilla.org/timer;1"].
  1043.                 createInstance(Ci.nsITimer);
  1044.             timer.initWithCallback(cryptoCallback, 0,
  1045.                 Ci.nsITimer.TYPE_ONE_SHOT);
  1046.         } else if (topic == "foxmarks-realstart") {
  1047.             // Real initialization starts here.
  1048.             FoxmarksSyncService = this;
  1049.             Xmarks.UpdateToXMarks();
  1050.             var dsList = loadDatasourceSet(true); 
  1051.  
  1052.             FoxmarksSyncService.nat = {}; 
  1053.             for(var x = 0; x < dsList.length; x++)
  1054.                 FoxmarksSyncService.nat[dsList[x].syncType] = dsList[x].WatchForChanges(this.server);
  1055.  
  1056.             if (!Xmarks.gSettings.wizardSuppress && !Xmarks.gSettings.useOwnServer &&
  1057.                     !Xmarks.gSettings.haveSynced) {
  1058.                 Xmarks.LogWrite("Starting Wizard: Never Synched");
  1059.                 FoxmarksLaunchSetupWizard();
  1060.             } else if (Xmarks.gSettings.majorVersion < 2) {
  1061.                 Xmarks.LogWrite("Starting Wizard: Major Upgrade");
  1062.                 Xmarks.gSettings.majorVersion = 3;
  1063.                 FoxmarksLaunchSetupWizard();
  1064.             } else {
  1065.                 // need to check for upgrades here
  1066.                 Xmarks.gSettings.majorVersion = 3;
  1067.                 var currver = Xmarks.FoxmarksVersion();
  1068.                 var lastver = Xmarks.gSettings.currVersion;
  1069.                 var ca = currver.split(".");
  1070.                 var la = lastver.split(".");
  1071.                 var newver =false;
  1072.                 
  1073.                 if(ca.length != la.length){
  1074.                     newver = true;
  1075.                 } else {
  1076.                     for(var x=0; x < ca.length-1; x++){
  1077.                         if(parseInt(ca[x]) != parseInt(la[x])){
  1078.                             newver = true;
  1079.                             break;
  1080.                         }
  1081.                     }
  1082.                 }
  1083.                 if(newver){
  1084.                     FoxmarksLaunchUpgradePage();
  1085.                 }
  1086.             }
  1087.             
  1088.             this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1089.             this.timer.initWithCallback(timerCallback, 1000*60,
  1090.                 Ci.nsITimer.TYPE_REPEATING_SLACK);
  1091.         } else if (topic == "foxmarks-tr") {
  1092.             this.pingServer(data);
  1093.  
  1094.         } else if (topic == "quit-application-requested") {
  1095.             HandleShutdown();
  1096.         } else if(topic == "em-action-requested"){
  1097.             subject.QueryInterface(Components.interfaces.nsIUpdateItem);
  1098.             if(subject.id == "foxmarks@kei.com"){
  1099.                 switch(data){
  1100.                     case 'item-uninstalled':
  1101.                         this._uninstall = true;
  1102.                         this.pingServer("uninstall");
  1103.                         break;
  1104.                     case 'item-disabled':
  1105.                         this.pingServer("disable");
  1106.                         break;
  1107.                     case 'item-cancel-action':
  1108.                         if(this._uninstall == false){
  1109.                             this.pingServer("cancel-disable");
  1110.                         } else {
  1111.                             this.pingServer("cancel-uninstall");
  1112.                         }
  1113.                         this._uninstall = false;
  1114.                         break;
  1115.                 }
  1116.  
  1117.             }
  1118.  
  1119.         } else if(topic == "quit-application-granted"){
  1120.             if(this._uninstall){
  1121.                 Xmarks.gSettings.clearAllPrefs();
  1122.             }
  1123.         } else if(topic == "foxmarks-showsettingpane"){
  1124.             if(data.length > 0){
  1125.                 Xmarks.OpenFoxmarksSettingsDialog(data);
  1126.             }
  1127.         } else if (topic == "foxmarks-unittesterror"){
  1128.             try {
  1129.                 ReturnErrorCode(parseInt(data));
  1130.             }
  1131.             catch(e){
  1132.                 Components.utils.reportError(e);
  1133.             }
  1134.         } else if (topic == "foxmarks-rununittest"){
  1135.             this.server.runUnitTest();
  1136.         } else if (topic == "foxmarks-datasourcechanged") {
  1137.             var a = data.split(';');
  1138.             this.lastmodified = parseInt(a[0]);
  1139.             var okState = (GetState() == "ready" || GetState() == "unknown");
  1140.             if (okState && Xmarks.gSettings.haveSynced &&
  1141.                 Xmarks.gSettings.isSyncEnabled(a[1]) && 
  1142.                 this.lastmodified > Date.parse(Xmarks.gSettings.lastSynchDate)) {
  1143.                 SetState("dirty");
  1144.             }
  1145.         } else {
  1146.             Xmarks.LogWrite("Yikes unknown topic " + topic);
  1147.         }
  1148.     },
  1149.  
  1150.     /////////////////////////////////////////////////////////////////////////
  1151.     // nsIFormSubmitObserver
  1152.     notify : function (formElement, aWindow, actionURI) {
  1153.         if(FoxmarksSyncService && FoxmarksSyncService.nat["passwords"]
  1154.                 && Xmarks && Xmarks.gSettings.isSyncEnabled("passwords")
  1155.                 ){
  1156.             Xmarks.LogWrite("Before Early Form Submit");
  1157.             FoxmarksSyncService.nat["passwords"].formsubmit(formElement);
  1158.             Xmarks.LogWrite("After Early Form Submit");
  1159.         }
  1160.         return true;
  1161.     },
  1162.  
  1163.     /////////////////////////////////////////////////////////////////////////
  1164.     // nsIClassInfo
  1165.     getInterfaces: function (aCount) {
  1166.         var interfaces = [Ci.nsIFoxmarksService,
  1167.         Ci.nsIObserver,
  1168.         Ci.nsIFormSubmitObserver,
  1169.         Ci.nsiRDFObserver];
  1170.         aCount.value = interfaces.length;
  1171.         return interfaces;
  1172.     },
  1173.  
  1174.     getHelperForLanguage: function (aLanguage) {
  1175.         return null;
  1176.     },
  1177.  
  1178.     get contractID() {
  1179.         return "@foxcloud.com/extensions/foxmarks;1";
  1180.     },
  1181.  
  1182.     get classDescription() {
  1183.         return "Foxmarks Service";
  1184.     },
  1185.  
  1186.     get classID() {
  1187.         return Components.ID("{49ace257-6111-48b2-a988-f9eb38b0fa58}");
  1188.     },
  1189.  
  1190.     get implementationLanguage() {
  1191.         return Ci.nsIProgrammingLanguage.JAVASCRIPT;
  1192.     },
  1193.  
  1194.     get flags() {
  1195.         return Ci.nsIClassInfo.SINGLETON;
  1196.     },
  1197.  
  1198.     /////////////////////////////////////////////////////////////////////////
  1199.     // nsISupports
  1200.     QueryInterface: function (aIID) {
  1201.         if (!aIID.equals(Ci.nsIFoxmarksService) &&
  1202.             !aIID.equals(Ci.nsISupports) &&
  1203.             !aIID.equals(Ci.nsIRDFObserver) &&
  1204.             !aIID.equals(Ci.nsIFormSubmitObserver) &&
  1205.             !aIID.equals(Ci.nsIObserver))
  1206.         throw Components.results.NS_ERROR_NO_INTERFACE;
  1207.         return this;
  1208.     }
  1209. };
  1210.  
  1211. var gModule = {
  1212.     _firstTime: true,
  1213.  
  1214.     registerSelf: function (aComponentManager, aFileSpec, aLocation, aType) {
  1215.         if (this._firstTime) {
  1216.             this._firstTime = false;
  1217.             throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  1218.         }
  1219.  
  1220.  
  1221.         aComponentManager = aComponentManager.
  1222.         QueryInterface(Ci.nsIComponentRegistrar);
  1223.  
  1224.         for (var key in this._objects) {
  1225.             var obj = this._objects[key];
  1226.             aComponentManager.registerFactoryLocation(obj.CID, obj.className,
  1227.                 obj.contractID, aFileSpec, aLocation, aType);
  1228.         }
  1229.  
  1230.         // Make the Foxmarks Service a startup observer
  1231.         var cm = Cc["@mozilla.org/categorymanager;1"].
  1232.         getService(Ci.nsICategoryManager);
  1233.         cm.addCategoryEntry("app-startup", this._objects.service.className,
  1234.             "service," + this._objects.service.contractID, true, true, null);
  1235.     },
  1236.  
  1237.  
  1238.     getClassObject: function (aComponentManager, aCID, aIID) {
  1239.         if (!aIID.equals(Ci.nsIFactory))
  1240.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1241.  
  1242.         for (var key in this._objects) {
  1243.             if (aCID.equals(this._objects[key].CID))
  1244.                 return this._objects[key].factory;
  1245.         }
  1246.  
  1247.         throw Components.results.NS_ERROR_NO_INTERFACE;
  1248.     },
  1249.  
  1250.     _makeFactory: #1= function(ctor) {
  1251.         return {
  1252.             createInstance: function (outer, iid) {
  1253.                 if (outer != null)
  1254.                     throw Components.results.NS_ERROR_NO_AGGREGATION;
  1255.                 return (new ctor()).QueryInterface(iid);
  1256.             }
  1257.         };
  1258.     },
  1259.  
  1260.     _objects: {
  1261.         service: { CID : nsFoxmarksService.prototype.classID,
  1262.             contractID : nsFoxmarksService.prototype.contractID,
  1263.             className  : nsFoxmarksService.prototype.classDescription,
  1264.             factory    : #1#(nsFoxmarksService)
  1265.         }
  1266.     },
  1267.  
  1268.     canUnload: function (aComponentManager)
  1269.     {
  1270.         return true;
  1271.     }
  1272. };
  1273.  
  1274. function NSGetModule(compMgr, fileSpec)
  1275. {
  1276.     return gModule;
  1277. }
  1278.